package com.boho.ecp.common;


import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

/**
 * 提供Cookie的统一管理。
 * 一个Cookie里可以存储多个key/value，有利于减少Cookie的数量。
 * 对于经常变动的key/value，应该单独存放在一个Cookie中，否则其它key/value也会被频繁读写。
 */
public class CookieManager {
	
	private final static Logger log = Logger.getLogger(CookieManager.class);
	
	/**
	 * 可以通过请求参数传递Cookie
	 */
	private static final String PARAM_COOKIES = "__cookies";
	
	/**
	 * Cookie编码方式
	 */
	private static final String COOKIE_ENCODING = "UTF-8";
	
	private final HttpServletRequest request;
	private final String securityCode;
	private final Set cookieNameSet = new HashSet();
	
	public CookieManager(HttpServletRequest request) {
		this(request, null);
	}
	
	public CookieManager(HttpServletRequest request, String securityCode) {
		this.request = request;
		this.securityCode = securityCode;
	}
	
	public CookieManager parse(String cookieName) {
		cookieNameSet.add(cookieName);
		return this;
	}

	private Map getCookies() {
        Map cookieMap = (Map) request.getAttribute(PARAM_COOKIES);
        if (cookieMap != null) {
            return cookieMap;
        }
        
        String paramCookies = request.getParameter(PARAM_COOKIES);
        if (StringUtils.isNotBlank(paramCookies)) {
        	cookieMap = parseItems(decrypt(paramCookies));
        	
        	for (Entry e : (Set<Entry>) cookieMap.entrySet()) {
        		e.setValue(parseItems((String) e.getValue()));
        	}
        } else {
	        cookieMap = new HashMap();
	        
	        Cookie[] cookies = request.getCookies();
	        if (cookies != null) {
		        for (int i = 0; i < cookies.length; i++) {
		            Cookie cookie = cookies[i];
		            String cookieName = cookie.getName();
		            if (!cookieNameSet.contains(cookieName)) {
		            	continue;
		            }
		            
		            Map items = parseItems(decrypt(unescape(cookie.getValue())));
		            Map itemMap = (Map) cookieMap.get(cookieName);
		            if (itemMap == null) {
		            	cookieMap.put(cookieName, items);
		            } else {
		            	itemMap.putAll(items);
		            }
		        }
	        }
        }
        
        request.setAttribute(PARAM_COOKIES, cookieMap);
        return cookieMap;
    }
	
	/**
	 * 加密
	 */
	private String encrypt(String str) {
		if (str == null) {
			return null;
		}
		
		if (StringUtils.isBlank(securityCode)) {
			return str;
		}
		
		byte[] bytes;
		try {
			bytes = str.getBytes("UTF-8");
		} catch (Exception ex) {
			log.error("failed to encrypt cookie value: " + str, ex);
			return null;
		}
		
		int mask = securityCode.hashCode();
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < bytes.length; i++) {
			int v = mask ^ bytes[i];
			sb.append(getChar(v));
			sb.append(getChar(v >>> 4));
		}
		
		return sb.toString();
	}
	
	/**
	 * 解密
	 */
	private String decrypt(String str) {
		if (str == null) {
			return null;
		}
		
		if (StringUtils.isBlank(securityCode)) {
			return str;
		}
		
		char[] ca = str.toCharArray();
		byte[] bytes = new byte[ca.length / 2];
		     
		int mask = securityCode.hashCode();
		for (int i = 0; i < bytes.length; i++) {
			int v = getInt(ca[i * 2]);
			v |= getInt(ca[i * 2 + 1]) << 4;
			bytes[i] = (byte) (v ^ mask);
		}
		
		try {
			return new String(bytes, "UTF-8");
		} catch (Exception ex) {
			log.error("failed to decrypt cookie value: " + str, ex);
			return null;
		}
	}
	
	private char getChar(int val) {
        int part = 0x0F & val;
        if (part < 10) {
        	return (char) ('0' + part);
        } else {
        	return (char) ('a' + part - 10);
        }
    }
	
	private int getInt(char c) {
		if (c <= '9') {
			return c - '0';
		} else {
			return c - 'a' + 10;
		}
	}
        
    private Map parseItems(String str) {
    	Map itemMap = new HashMap();
    	if (StringUtils.isBlank(str)) {
    		return itemMap;
    	}
    	
    	String[] items = str.split("&");
        for (int i = 0; i < items.length; i++) {
        	String item = items[i];
            if (StringUtils.isBlank(item)) {
            	continue;
            }

            int offset = item.indexOf('=');
            if (offset > 0) {
            	String key = item.substring(0, offset);
            	String value = item.substring(offset + 1);
            	
            	if (StringUtils.isNotBlank(key) && StringUtils.isNotBlank(value)) {
            		itemMap.put(key, unescape(value));
                }
            }
        }
        
        return itemMap;
    }
    
    /**
     * 得到Cookie中的一项值
     */
    public String getValue(String cookieName, String key) {
    	return getValue(cookieName, key, null);
    }
    
    /**
     * 得到Cookie中的一项值，若该项不存在，则返回默认值
     */
    public String getValue(String cookieName, String key, String defaultValue) {
        Map itemMap = (Map) getCookies().get(cookieName);
        if (itemMap == null) {
        	return defaultValue;
        }
        
        String value = (String) itemMap.get(key);
        return StringUtils.isNotBlank(value) ? value : defaultValue;
    }
    
    /**
     * 设置Cookie的一项值
     */
    public void setValue(String cookieName, String key, String value) {
    	Map cookieMap = getCookies();
    	
        Map itemMap = (Map) cookieMap.get(cookieName);
        if (itemMap == null) {
        	itemMap = new HashMap();
        	cookieMap.put(cookieName, itemMap);
        }
        
        if (StringUtils.isNotBlank(value)) {
        	itemMap.put(key, value);
        } else {
        	itemMap.remove(key);
        }
    }
    
    private String joinItems(Map itemMap) {
    	if (itemMap == null) {
    		return "";
    	}
    	
    	StringBuffer sb = new StringBuffer();

    	for (Entry e : (Set<Entry>) itemMap.entrySet()) {
            String key = (String) e.getKey();
        	String value = (String) e.getValue();
        	
        	if (StringUtils.isNotBlank(key) && StringUtils.isNotBlank(value)) {
        		if (sb.length() > 0) {
        			sb.append('&');
        		}
        		
        		sb.append(key);
        		sb.append('=');
				sb.append(escape(value));
        	}
        }
        
        return sb.toString();
    }
    
    private void addCookie(HttpServletResponse response, String domain, String path,
    		String cookieName, String cookieValue, int age) {
    	
        Cookie cookie = new Cookie(cookieName, escape(encrypt(cookieValue)));
        cookie.setMaxAge(age);
        
        if (StringUtils.isNotBlank(domain)) {
        	cookie.setDomain(domain);
        }
        
        if (StringUtils.isNotBlank(path)) {
        	cookie.setPath(path);
        }
        
        response.addCookie(cookie);
    }
    
    /**
     * 保存Cookie
     */
    public void saveCookie(HttpServletResponse response, 
    		String domain, String path, String cookieName, int age) {
    	
        addCookie(response, domain, path, cookieName, 
        		joinItems((Map) getCookies().get(cookieName)), age);
    }
    
    /**
     * 删除Cookie
     */
    public void removeCookie(HttpServletResponse response, 
    		String domain, String path, String cookieName) {
    	
    	saveCookie(response, domain, path, cookieName, 0);
    }
    
    private String escape(String str) {
    	if (StringUtils.isNotBlank(str)) {
	    	try {
				return URLEncoder.encode(str, COOKIE_ENCODING);
	    	} catch (Exception ex) {
				log.error("failed to encode cookie value: " + str, ex);
			}
    	}
    	
    	return "";
    }
    
    private String unescape(String str) {
    	if (StringUtils.isNotBlank(str)) {
	    	try {
				return URLDecoder.decode(str, COOKIE_ENCODING);
	    	} catch (Exception ex) {
				log.error("failed to decode cookie value: " + str, ex);
			}
    	}
    	
    	return "";
    }
    
    /**
     * 获得HTTP请求参数形式的Cookie
     */
    public String getCookieParameter() {
    	Map map = new HashMap();
    	
    	for (Entry e : (Set<Entry>) getCookies().entrySet()) {
    		map.put(e.getKey(), joinItems((Map) e.getValue()));
    	}
    	
    	return encrypt(joinItems(map));
    }
    
}
